/*
DB Autobackuper plugin for Miranda IM

Copyright (C) 2005-2012 chaos.persei, sje, Kildor, Billy_Bons, Vasilich, Nikolay Raspopov

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "stdafx.h"
#include "options.h"

static UINT_PTR timer_id = 0;

INT_PTR DBSaveAs(WPARAM /*wParam*/, LPARAM /*lParam*/)
{
	TCHAR fname_buff[ MAX_PATH ] = {};
	CallService( MS_DB_GETPROFILENAMET, MAX_PATH, (LPARAM)fname_buff );
	_tcscat_s( fname_buff, MAX_PATH, _T(".zip") );

	TCHAR szFilter[ MAX_PATH ] = {};
	size_t i = mir_sntprintf(szFilter, 64, _T("%s (*.dat.zip)"), TranslateT("Zipped Miranda Databases") ) + 1;
	_tcscpy_s( szFilter + i, SIZEOF(szFilter) - i, _T("*.dat.zip") ); 
	i += 10;
	i += mir_sntprintf( szFilter + i, 48, _T("%s (*.*)"), TranslateT("All Files") ) + 1;
	_tcscpy_s( szFilter + i, SIZEOF(szFilter) - i, _T("*"));
	szFilter[i + 2] = 0;

	OPENFILENAME ofn = {};
	ofn.lStructSize = sizeof( ofn );
	ofn.lpstrFile = fname_buff;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT;
	ofn.lpstrFilter = szFilter;
	ofn.nFilterIndex = 1;
	ofn.lpstrDefExt = _T("dat.zip");

	if ( GetSaveFileName( &ofn ) )
		mir_forkthread( BackupThread, (void*)fname_buff );

	return 0;
}

int RotateBackups()
{
	struct
	{
		TCHAR Name[ MAX_PATH ];
		FILETIME CreationTime;
	} FileNameFound = {};

	LPCTSTR backupfolder = Utils_ReplaceVarsT( options.folder );

	TCHAR backupfolderWild[ MAX_PATH ];
	_tcscpy_s( backupfolderWild, MAX_PATH, backupfolder );
	PathAddBackslash( backupfolderWild );
	_tcscat_s( backupfolderWild, MAX_PATH, _T("*") );

	unsigned int i;
	do
	{
		i = 0;
		*FileNameFound.Name = _T('\0');

		WIN32_FIND_DATA FindFileData = {};
		HANDLE hFind = FindFirstFile( backupfolderWild, &FindFileData );
		if ( hFind != INVALID_HANDLE_VALUE ) 
		{
			do
			{
				if ( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
					continue;

				size_t len = _tcslen( FindFileData.cFileName );
				if ( len < 8 || _tcsicmp( FindFileData.cFileName + len - 8, _T(".dat.zip") ) != 0 )
					continue;

				if ( ! *FileNameFound.Name )
				{
					_tcscpy_s( FileNameFound.Name, MAX_PATH, FindFileData.cFileName );
					FileNameFound.CreationTime = FindFileData.ftCreationTime;
				}
				else if ( ( FindFileData.ftCreationTime.dwHighDateTime < FileNameFound.CreationTime.dwHighDateTime ) ||
					( FindFileData.ftCreationTime.dwHighDateTime == FileNameFound.CreationTime.dwHighDateTime &&
					  FindFileData.ftCreationTime.dwLowDateTime < FileNameFound.CreationTime.dwLowDateTime ) )
				{
					_tcscpy_s( FileNameFound.Name, MAX_PATH, FindFileData.cFileName );
					FileNameFound.CreationTime = FindFileData.ftCreationTime;
				}

				i++;
			}
			while ( FindNextFile( hFind, &FindFileData ) );
			FindClose( hFind );
		}

		if ( i >= options.num_backups && *FileNameFound.Name )
		{
			TCHAR backupfilename1[ MAX_PATH ] = {};
			mir_sntprintf( backupfilename1, MAX_PATH, _T("%s\\%s"), backupfolder, FileNameFound.Name );
			if ( ! DeleteFile( backupfilename1 ) )
				// File error
				break;
		}
	}
	while ( i > options.num_backups );

	mir_free( (void*)backupfolder );

	return 0;
}

bool ZipFile(LPCTSTR szSource, LPCTSTR szDest)
{
	bool ret = false;

	LPCSTR szDestPath = mir_u2a( szDest );
	LPCSTR szSourceName = mir_u2a( PathFindFileName( szSource ) );

	WIN32_FILE_ATTRIBUTE_DATA fad = {};
	if ( GetFileAttributesEx( szSource, GetFileExInfoStandard, &fad ) )
	{
		SYSTEMTIME st;
		FileTimeToLocalFileTime(  &fad.ftLastWriteTime, &fad.ftLastWriteTime );
		FileTimeToSystemTime( &fad.ftLastWriteTime, &st );

		HANDLE hSrc = CreateFile( szSource, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
		if ( hSrc != INVALID_HANDLE_VALUE )
		{
			if ( zipFile hZip = zipOpen( szDestPath, APPEND_STATUS_CREATE ) )
			{
				zip_fileinfo fi = {};
				fi.tmz_date.tm_sec = st.wSecond;
				fi.tmz_date.tm_min = st.wMinute;
				fi.tmz_date.tm_hour = st.wHour;
				fi.tmz_date.tm_mday = st.wDay;
				fi.tmz_date.tm_mon = st.wMonth;
				fi.tmz_date.tm_year = st.wYear;

				int res = zipOpenNewFileInZip( hZip, szSourceName, &fi, NULL, 0, NULL, 0, "",  Z_DEFLATED, Z_BEST_COMPRESSION );
				if ( res == ZIP_OK )
				{
					DWORD buf_length = 256 * 1024;	// 256 KB
					if ( void* buf = mir_alloc( buf_length ) )
					{
						for (;;)
						{
							DWORD read = 0;
							if ( ! ReadFile( hSrc, buf, buf_length, &read, NULL ) )
								break;

							if ( read == 0 )
							{
								// EOF
								ret = true;
								break;
							}

							res = zipWriteInFileInZip( hZip, buf, read );
							if ( res != ZIP_OK )
								break;
						}

						mir_free( buf );
					}
					zipCloseFileInZip( hZip );
				}
				zipClose( hZip, "Miranda IM Database\r\nCreated by: " __PLUGIN_NAME " " __VERSION_STRING "\r\n" __URL );
			}
			CloseHandle( hSrc );
		}
	}

	mir_free( (void*)szSourceName );
	mir_free( (void*)szDestPath );

	return ret;
}

void BackupThread(void* backup_filename)
{
	CallService( MS_SYSTEM_THREAD_PUSH, 0, 0 );

	CoInitialize( NULL );

	Backup( (LPCTSTR)backup_filename );

	CoUninitialize();

	CallService( MS_SYSTEM_THREAD_POP, 0, 0 );
}

int Backup(LPCTSTR backup_filename)
{
	TCHAR dbname[ MAX_PATH ] = {};
	CallService( MS_DB_GETPROFILENAMET, MAX_PATH, (LPARAM)dbname );

	LPCTSTR profilepath = Utils_ReplaceVarsT( MIRANDA_USERDATAT );

	TCHAR source_file[ MAX_PATH ];
	_tcscpy_s( source_file, MAX_PATH, profilepath );
	PathAddBackslash( source_file );
	_tcscat_s( source_file, MAX_PATH,  dbname );

	TCHAR dest_file[ MAX_PATH ] = {};
	if ( backup_filename == NULL )
	{
		TCHAR computer[ MAX_COMPUTERNAME_LENGTH + 1 ] = {};
		DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
		GetComputerName( computer, &size );
		_tcslwr_s( computer, MAX_COMPUTERNAME_LENGTH + 1 );

		SYSTEMTIME st = {};
		GetLocalTime( &st );

		LPCTSTR backupfolder = Utils_ReplaceVarsT( options.folder );

		// ensure the backup folder exists (either create it or return non-zero signifying error)
		int err = CreateDirectoryTree( backupfolder );
		if ( err != ERROR_ALREADY_EXISTS && err != 0 )
			return 1;

		_tcscpy_s( dest_file, MAX_PATH, backupfolder );
		PathAddBackslash( dest_file );
		mir_sntprintf( dest_file + _tcslen( dest_file ), MAX_PATH - _tcslen( dest_file ), _T("%s_%02d.%02d.%02d@%02d-%02d-%02d_%s.zip"),
			computer, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, dbname );

		mir_free( (void*)backupfolder );
	}
	else
	{
		LPCTSTR backup_filename_ext = Utils_ReplaceVarsT( backup_filename );

		_tcsncpy_s( dest_file, MAX_PATH, backup_filename_ext, MAX_PATH - 1 );

		mir_free( (void*)backup_filename_ext );
	}

	RotateBackups();

	bool zipped = false;
	for ( int i = 0; i < 10; ++i )
	{
		if ( ZipFile( source_file, dest_file ) )
		{
			zipped = true;
			break;
		}
		
		DeleteFile( dest_file );
		Sleep( 200 );
	}

	if ( zipped )
	{
		DBWriteContactSettingDword( 0, "AutoBackups", "LastBackupTimestamp", (DWORD)time( NULL ) );

		if ( ! options.disable_popups )
		{
			TCHAR szMsg[ 200 ];
			_tcscpy_s( szMsg, SIZEOF( szMsg ), TranslateT("Database backuped:") );
			_tcscat_s( szMsg, SIZEOF( szMsg ), _T("\r\n") );
			PathCompactPathEx( szMsg + _tcslen( szMsg ), dest_file, (UINT)( SIZEOF( szMsg ) - _tcslen( szMsg ) - 1 ), 0 );
			ShowPopup( szMsg, MB_ICONINFORMATION );
		}
	}
	else
	{
		TCHAR szMsg[ 200 ];
		_tcscpy_s( szMsg, SIZEOF( szMsg ), TranslateT("Error creating backup:") );
		_tcscat_s( szMsg, SIZEOF( szMsg ), _T("\r\n") );
		PathCompactPathEx( szMsg + _tcslen( szMsg ), dest_file, (UINT)( SIZEOF( szMsg ) - _tcslen( szMsg ) - 1 ), 0 );
		ShowPopup( szMsg, MB_ICONERROR );
	}

	mir_free( (void*)profilepath );

	return 0;
}

VOID CALLBACK TimerProc(HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR /*idEvent*/, DWORD /*dwTime*/)
{
	if ( options.backup_types & BT_PERIODIC )
	{
		time_t t = time( NULL );
		time_t diff = t - (time_t)DBGetContactSettingDword( 0, "AutoBackups", "LastBackupTimestamp", (DWORD)t );

		if ( diff > (time_t)( options.period * ( ( options.period_type == PT_MINUTES ) ? 60 : ( ( options.period_type == PT_HOURS ) ? 60 * 60 : 60 * 60 * 24 ) ) ) )
			Backup( NULL );
	}
}

int SetBackupTimer()
{
	if ( options.backup_types & BT_PERIODIC )
	{
		timer_id = SetTimer( NULL, timer_id, 1000, TimerProc );
	} 
	else if ( timer_id != 0 ) 
	{
		KillTimer( NULL, timer_id );
		timer_id = 0;
	}
	return 0;
}

INT_PTR ABService(WPARAM wParam, LPARAM /*lParam*/)
{
	mir_forkthread( BackupThread, (void*)wParam );

	return 0;
}
